#include <iostream>
#include <SFML/graphics.hpp>
#include <vector>
#include <functional>

#include "en.hpp"
#include "path.h"
#include "common.hpp"
#include "physics.hpp"

using namespace std;

template<typename T, typename V, typename... params>
struct single_press_object
{
    int side = 0;
    bool isdown = false;

    T input;
    V key;

    function<params...> func;

    single_press_object(function<params...> pf);

    void tick();
};

template<typename V, typename... params>
struct single_press_object<sf::Mouse, V, params...>
{
    int side = 0;
    bool isdown = false;

    sf::Mouse input;
    V key;

    function<params...> func;

    single_press_object(V k, function<params...> pf)
    {
        func = pf;
        key = k;
    }

    void tick()
    {
        if(input.isButtonPressed(key) && !isdown)
        {
            func(input.getPosition(en::win).x, input.getPosition(en::win).y);
            isdown = true;
        }
        if(!input.isButtonPressed(key))
        {
            isdown = false;
        }
    }
};

template<typename V, typename... params>
struct single_press_object<sf::Keyboard, V, params...>
{
    int side = 0;
    bool isdown = false;

    sf::Keyboard input;
    V key;

    function<params...> func;

    single_press_object(V k, function<params...> pf)
    {
        func = pf;
        key = k;
    }

    void tick()
    {
        if(input.isKeyPressed(key) && !isdown)
        {
            func();
            isdown = true;
        }
        if(!input.isKeyPressed(key))
        {
            isdown = false;
        }
    }
};

void spawn_box(int x, int y)
{
    peon p;

    p.x = x;
    p.y = y;

    p.col = sf::Color(100, 100, 200);

    p.push();
}

sf::Color box_col(200, 80, 80);
pathable cur_pth = PATHABLE;

void spawn_square(int x, int y)
{
    static square sq;
    static bool spawn = false;

    if(!spawn)
    {
        sq.ax = floor(x/10.0f)*10.0f;
        sq.ay = floor(y/10.0f)*10.0f;
        sq.col = box_col;
        sq.is_pathable = cur_pth;
    }
    if(spawn)
    {
        sq.bx = floor(x/10.0f)*10.0f;
        sq.by = floor(y/10.0f)*10.0f;
        en::squares.push_back(sq);
    }

    spawn = !spawn;
}
void spawn_square_2(int* x, int* y)
{
    static square sq;
    static bool spawn = false;

    if(!spawn)
    {
        sq.ax = floor(*x/10.0f)*10.0f;
        sq.ay = floor(*y/10.0f)*10.0f;
        sq.col = box_col;
        sq.is_pathable = cur_pth;
    }
    if(spawn)
    {
        sq.bx = floor(*x/10.0f)*10.0f;
        sq.by = floor(*y/10.0f)*10.0f;
        en::squares.push_back(sq);
    }

    spawn = !spawn;
}

void spawn_cur_box(int x, int y, int* c_num)
{
    block b;
    b.x = floor(x/10.0f)*10.0f;
    b.y = floor(y/10.0f)*10.0f;

    string name = en::art_names[*c_num];

    if(name == "wall" || name == "door")
        b.y -= 1;

    if(name == "wallv" || name == "doorv")
        b.x -= 1;

    b.art_num = *c_num;
    b.type_id = *c_num;

    b.init(*c_num);

    b.trait_nums[0] = 30;

    b.push();
}

void toggle_box_col(int x, int y)
{
    if(box_col == box_col)
        box_col = sf::Color(100, 200, 100);
    else
        box_col = box_col;

    if(cur_pth == PATHABLE)
        cur_pth = NON_PATHABLE;
    else
        cur_pth = PATHABLE;
}

void door_rot(int* n)
{
    *n = !(*n);
}

void job_mouse(int x, int y)
{
    job j;
    j.x = x;
    j.y = y;
    j.ticks_to_complete = 100;
    j.priority = 10;

    if(x>=0 && x<en::w && y >= 0 && y < en::h)
    {
        if(en::collision_win[x][y] != PATHABLE)
        {
            return; ///destination not pathable
        }
    }
    else
        return; ///out of bounds

    j.push();
}

void particle_spawner(int* x, int* y)
{
    if(dens_prev==NULL)
        return;

    particle p;
    p.x = *x;
    p.y = *y;
    //add_phys(*x, *y, 10);
    //for(int i=0; i<10; i++)
    //particle_list.push_back(p);


    //dens_prev[*x/(800/80) + (62*(*y))/60] = 1.0f;
    int px, py;
    px = *x / en::map_const_x;
    py = *y / en::map_const_y;

    static int previous_x, previous_y;

    if(px < 0 || px >= c_map_x || py < 0 || py >= c_map_y)
    {
        return;
    }

    //dens_prev[px + c_map_x*py] = 10.0f;

    int pin = px;
    int yin = py;

    /*for(; px<pin+80; px++)
    {
        for(py = yin; py<yin+80; py++)
        {
            dens_prev[px + c_map_x*py] = 0.0f;
            u_prev[px + c_map_x*py] = 0;
            v_prev[px + c_map_x*py] = 0;
        }
    }*/


    //dens_prev[5 + c_map_x*5] = 0.5f;

    //u_prev[5 + c_map_x*5] = 0.6/60.0f;
    //v_prev[5 + c_map_x*5] = 0.6/60.0f;

    if(previous_x == 0 && previous_y == 0)
    {
        previous_x = px;
        previous_y = py;
    }

    /*u_prev[px + c_map_x*py] = -(previous_x - *x)/60.0f;
    v_prev[px + c_map_x*py] = -(previous_y - *y)/60.0f;*/


    previous_x = *x;
    previous_y = *y;
}

/*{"crew_q",
"mess",
"medical",
"engineering",
"cockpit",
"shields",
"armour",
"engines",
"lasers",
"missiles",
"ammo_major",
"ammo_minor",
"wall",
"wallv",
"door",
"doorv"}*/

int main()
{
    en::w = 800;
    en::h = 600;

    en::init();

    en::load_traits();

    en::win.create(sf::VideoMode(en::w, en::h), "test");

    en::collision_win = new pathable*[en::w];

    for(int i=0; i<en::w; i++)
        en::collision_win[i] = new pathable[en::h];

    sf::Event event;

    sf::Clock c;

    float otime=c.getElapsedTime().asMilliseconds(), ctime=c.getElapsedTime().asMilliseconds();

    float frametime = 0;

    int c_pic = 0;

    string base = "pics/";
    string ext = ".bmp";

    vector<sf::Image> art;

    ///load art into images
    for(auto& i : en::art_names)
    {
        string file = base + i + ext;
        sf::Image img;
        img.loadFromFile(file.c_str());
        art.push_back(img);
    }

    ///load art into textures
    for(auto& i : art)
    {
        sf::Texture tex;
        tex.loadFromImage(i);
        en::art_t.push_back(tex);
    }



    int mx = 0, my = 0;

    ///declares all the button press listeners
    single_press_object<sf::Mouse, sf::Mouse::Button, decltype(spawn_box)> box(sf::Mouse::Right, spawn_box);
    //single_press_object<sf::Mouse, sf::Mouse::Button, void(int,int)> sqr(sf::Mouse::Left, spawn_square);
    //single_press_object<sf::Mouse, sf::Mouse::Button, void(int,int)> sqr(sf::Mouse::Left, bind(spawn_cur_box, placeholders::_1, placeholders::_2, &c_pic));
    single_press_object<sf::Mouse, sf::Mouse::Button, void(int,int)> box_toggle(sf::Mouse::Middle, spawn_square);
    single_press_object<sf::Mouse, sf::Mouse::Button, void(int,int)> mouse_job(sf::Mouse::XButton1, job_mouse);

    single_press_object<sf::Keyboard, sf::Keyboard::Key, void()> save_level(sf::Keyboard::S, en::save);
    single_press_object<sf::Keyboard, sf::Keyboard::Key, void()> load_level(sf::Keyboard::A, en::load);
    //single_press_object<sf::Keyboard, sf::Keyboard::Key, void()> rot_doors(sf::Keyboard::D, bind(door_rot, &holder_rot));
    single_press_object<sf::Keyboard, sf::Keyboard::Key, void()> sq_spn(sf::Keyboard::F, bind(spawn_square_2, &mx, &my));

    //single_press_object<sf::Keyboard, sf::Keyboard::Key, void()> spawn_particle(sf::Keyboard::G, bind(particle_spawner, &mx, &my));

    sf::Keyboard key;
    sf::Mouse mouse;

    bool do_phys = false;

    sf::Image scr;
    scr.create(en::w, en::h);


    while(en::win.isOpen())
    {
        frametime = ctime - otime;
        otime = ctime;
        ctime = c.getElapsedTime().asMilliseconds();
        c.restart();

        ///window poll loop
        while(en::win.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
            {
                en::win.close();
                exit(0);
                break;
            }
            if(event.type == sf::Event::MouseWheelMoved)
            {
                c_pic += event.mouseWheel.delta;

                if(c_pic < 0)
                    c_pic = 0;
                if(c_pic >= en::art_names.size())
                    c_pic = en::art_names.size()-1;
            }
        }

        mx = mouse.getPosition(en::win).x;
        my = mouse.getPosition(en::win).y;

        ///button press listeners do their thing
        box.tick();
        //sqr.tick();
        box_toggle.tick();
        //rot_doors.tick();

        save_level.tick();
        load_level.tick();

        mouse_job.tick();
        sq_spn.tick();

        //spawn_particle.tick();



        if(mouse.isButtonPressed(sf::Mouse::Right))
        {
            spawn_box(mouse.getPosition(en::win).x, mouse.getPosition(en::win).y);
        }
        if(mouse.isButtonPressed(sf::Mouse::Left))
        {
            spawn_cur_box(mouse.getPosition(en::win).x, mouse.getPosition(en::win).y, &c_pic);
        }

        if(key.isKeyPressed(sf::Keyboard::U) || do_phys)
        {

            do_phys = true;

            if(en::render)
                particle_spawner(&mx, &my);

            if(en::render)
                physics(ctime);
        }

        ///main processing tick
        en::tick();

        ///draw the currently selected object to the screen
        sf::Sprite spr;
        spr.setTexture(en::art_t[c_pic]);
        spr.setPosition(10, 10);
        spr.setScale(2, 2);
        en::win.draw(spr);


        //en::display();
        /*for(int i=0; i<en::w; i++)
        {
            for(int j=0; j<en::h; j++)
            {
                //scr.setPixel(i, j, en::collision_win[i][j]==NON_PATHABLE ? sf::Color(255, 0, 0) : sf::Color(0,0,255));
                if(wind_map!=NULL)
                {
                    float val = wind_map[i][j]*1000;
                    if(val > 255)
                    {
                        val = 255;
                    }
                    scr.setPixel(i, j, sf::Color(val, 0, 0, 128));
                }
                else
                    scr.setPixel(i, j, sf::Color(0, 0, 0, 0));
            }
        }*/
        if(en::render)
        for(int i=1; i<en::w-1; i++)
        {
            for(int j=1; j<en::h-1; j++)
            {
                if(dens_prev!=NULL)
                {
                    /*int px, py;
                    px = i / ceilf(1024.0f/c_map_x);
                    py = j / ceilf(768.0f/c_map_y);


                    float val = dens_prev[(px) + (c_map_x*py)]*5550;
                    float rval = 0;
                    if(val > 255)
                    {
                        rval = (int)val % 255;
                        val = 255;
                    }
                    scr.setPixel(i, j, sf::Color(rval, 0, val, 255));*/


                    int px = i/en::map_const_x;
                    int py = j/en::map_const_y;

                    /*
                    float xdu = u_prev[px+1 + c_map_x*py] - u_prev[px + c_map_x*py];
                    float ydu = u_prev[px + c_map_x*(py+1)] - u_prev[px + c_map_x*py];

                    float ugrad_abs = (sqrt(xdu*xdu + ydu*ydu));

                    float xdv = v_prev[px+1 + c_map_x*(py)] - v_prev[px + c_map_x*(py)];
                    float ydv = v_prev[px + c_map_x*(py+1)] - v_prev[px + c_map_x*(py)];

                    float vgrad_abs = sqrt(xdv*xdv + ydv*ydv);

                    float grad_abs = sqrt(ugrad_abs*ugrad_abs + vgrad_abs*vgrad_abs)*5000000;

                    if(grad_abs > 255)
                        grad_abs = 255;
                    if(grad_abs < 0)
                        grad_abs = 0;*/

                    float neg = 0;

                    //float grad_abs = fabs(u_prev[px + c_map_x*py]) + fabs(v_prev[px + c_map_x*py])*1000000;

                    float uval = u_prev[px + c_map_x*py];
                    float vval = v_prev[px + c_map_x*py];

                    float grad_abs = sqrtf(uval*uval + vval*vval)*1000000;

                    if(grad_abs > 255)
                        grad_abs = 255;
                    if(grad_abs < 0)
                    {
                        grad_abs = 0;
                    }

                    scr.setPixel(i, j, sf::Color(grad_abs, 0, 0, 255));
                }
                else
                    scr.setPixel(i, j, sf::Color(0, 0, 0, 0));
            }
        }

        sf::Texture tex;
        tex.loadFromImage(scr);
        sf::Sprite spr2(tex);
        if(en::render)
        en::win.draw(spr2);

        /*for(auto& i : particle_list)
        {
            sf::CircleShape shp(4);
            shp.setPosition(i.x, i.y);
            shp.setFillColor(sf::Color(0, 255, 0));
            if(en::render)
                en::win.draw(shp);

        }*/

        en::display();
        en::clear();

        if(key.isKeyPressed(sf::Keyboard::Space))
        {
            cout << en::peons.size() << endl;
            cout << "f " << ctime << endl;
        }
    }

    return 0;
}
